// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: jhkim, lucasyoo

#include "coin2/strategy/cmm_strat/cmm_pricer.h"

namespace coin2::strategy::cmm_strat {

std::optional<double> CmmSelfMidpPricer::GetMid() {
  if (price_a_bbo_.first == 0.0 || price_a_bbo_.second == 0.0) {
    return std::nullopt;
  }
  return (price_a_bbo_.first + price_a_bbo_.second) / 2.0;
}

CmmSelfMidpPricer::CmmSelfMidpPricer(
    const ::nlohmann::json& self_mid_config,
    std::optional<int64_t> ts) {
  int64_t strategy_created_time(ts.has_value() ? *ts : GetCurrentTimestamp());
  product_a_ = CreateProductFromUniqueString(
      self_mid_config["product_a"].get<std::string>(),
      strategy_created_time);
  product_ids_.insert(GetProductId(*product_a_.get()));
  extra_bp_ = self_mid_config["extra_bp"].get<double>();
}

void CmmSelfMidpPricer::onBookFeed(const FeedUpdate& upd) {
  auto prod_id = GetProductId(upd.product());
  auto symbol = upd.product().absolute_norm();
  auto it = product_ids_.find(prod_id);

  // Update into new bbo info
  if (it == product_ids_.end()) {
    SPDLOG_WARN("Not in product_ids_ {}, {}", prod_id, symbol);
    fair_price_ = {0.0, 0.0};
    return;
  } else {
    auto book = upd.GetBookBuilder();
    auto ask0 = book->Ask0();
    auto bid0 = book->Bid0();

    if (book->AskSize() == 0 || book->BidSize() == 0) {
      fair_price_ = {0.0, 0.0};
      return;
    }

    if (prod_id == GetProductId(*product_a_.get())) {
      price_a_bbo_ = {bid0->price, ask0->price};
    } else {
      SPDLOG_WARN("Unknown product {}", symbol);
      fair_price_ = {0.0, 0.0};
      return;
    }
  }

  // Check if bbo price is not updated
  if (price_a_bbo_.first == 0.0 || price_a_bbo_.second == 0.0) {
    SPDLOG_WARN(
        "{} needs bbo update. Currently: {} {}",
        product_a_->absolute_norm(),
        price_a_bbo_.first,
        price_a_bbo_.second);
    fair_price_ = {0.0, 0.0};
    return;
  }

  pi_ = &(product_cache_.holder(*product_a_.get()).product_info());

  const auto& tick_price_policy = pi_->tick_price();
  const double tick_size = tick_price_policy.UpTicksize(
      tick_price_policy.Round((price_a_bbo_.first + price_a_bbo_.second) / 2.0));

  // If Current bbo spread <= tick_size -> no trade
  if (price_a_bbo_.second - price_a_bbo_.first <= tick_size * (1 + 1e-4)) {
    SPDLOG_INFO(
        "{} spread is less than tick size {}. Bid: {} Ask: {}",
        product_a_->absolute_norm(),
        tick_size,
        price_a_bbo_.first,
        price_a_bbo_.second);

    fair_price_ = {0.0, 0.0};
    return;
  }

  /*
  Pricing Part
  */
  // Get the bid/ask price using arbitrage condition
  double extra_bp_rate = extra_bp_ * 1e-4;

  // Get the mid price
  double midp = (price_a_bbo_.first + price_a_bbo_.second) / 2.0;

  /* NEW PRICING LOGIC */
  double priced_a_bbo_ask = (midp) * (1 + extra_bp_rate);
  double priced_a_bbo_bid = (midp) * (1 - extra_bp_rate);

  if (priced_a_bbo_bid <= price_a_bbo_.first) {
    priced_a_bbo_bid = price_a_bbo_.first + tick_size;
  }

  if (priced_a_bbo_ask >= price_a_bbo_.second) {
    priced_a_bbo_ask = price_a_bbo_.second - tick_size;
  }

  // Rounding the Price
  priced_a_bbo_ask =
      tick_price_policy.RoundPassively(priced_a_bbo_ask, OrderDirection::DIRECTION_SELL);
  priced_a_bbo_bid =
      tick_price_policy.RoundPassively(priced_a_bbo_bid, OrderDirection::DIRECTION_BUY);

  // Update the fair_price_
  fair_price_ = {priced_a_bbo_bid, priced_a_bbo_ask};
}

}  // namespace coin2::strategy::cmm_strat
